/****************************************************************************
 * Copyright (C) 2012
 * by Dj_Skual
 *
 * This software is provided 'as-is', without any express or implied
 * warranty. In no event will the authors be held liable for any
 * damages arising from the use of this software.
 *
 * Permission is granted to anyone to use this software for any
 * purpose, including commercial applications, and to alter it and
 * redistribute it freely, subject to the following restrictions:
 *
 * 1. The origin of this software must not be misrepresented; you
 * must not claim that you wrote the original software. If you use
 * this software in a product, an acknowledgment in the product
 * documentation would be appreciated but is not required.
 *
 * 2. Altered source versions must be plainly marked as such, and
 * must not be misrepresented as being the original software.
 *
 * 3. This notice may not be removed or altered from any source
 * distribution.
 *
 * MemCardHandle.cpp
 *
 * for SaveGame Manager GX 2012
 ***************************************************************************/
#include "MemCardHandle.hpp"
#include "../SoundOperations/MusicPlayer.h"
#include "../Themes/CTheme.h"
#include "../Memory/mem2.h"

#define MAXFILEBUFFER (1024 * 2048)	//! 2MB Buffer

MemCardHandle::MemCardHandle(int cardSlot)
{
	CardSlot = cardSlot;
	Mounted = false;
	SectorSize = 0;
	SysArea = NULL;
}

MemCardHandle::~MemCardHandle()
{
	UnMount();
}

int MemCardHandle::InternalMount()
{
	int ret = -1;
	int tries = 0;
	
	if(!SysArea)
		SysArea = (u8*)MEM1_memalign(32, CARD_WORKAREA);
	if(!SysArea)
		return CARD_ERROR_BUSY;
	
	while((tries < 50) && (ret < CARD_ERROR_READY))			
	{
		EXI_ProbeReset();
		
		ret = CARD_Mount(CardSlot, SysArea, card_removed);
		if (ret >= 0)
			break;
		
		tries++;
	}
	
	s32 memsize, sectsize;
	ret = CARD_ProbeEx(CardSlot, &memsize, &sectsize);
	if (memsize > 0 && sectsize > 0)
	{
		Mounted = true;
		ReadSectorSize();
	}
	else
	{
		UnMount();
		return -1;
	}
	
	return ret;
}

bool MemCardHandle::Mount()
{
	if(!IsInserted()) return false;
	if(IsMounted()) return true;
	
	CARD_Init(NULL, NULL);
	
	int ret = InternalMount();
	if(ret < 0)
		return false;
	
	gprintf("card%s mounted\n", (CardSlot == CARD_SLOTA) ? "A" : "B");
	
	return true;
}

bool MemCardHandle::IsInserted()
{
	int ret = CARD_ProbeEx(CardSlot, NULL, NULL);
	if(ret == CARD_ERROR_WRONGDEVICE || ret == CARD_ERROR_NOCARD)
		return false;
	
	return true;
}

void MemCardHandle::card_removed(s32 cardSlot UNUSED, s32 result UNUSED)
{
	//gprintf("\n\tcard was removed from slot %s", (cardSlot == CARD_SLOTA) ? "A" : "B");
}

void MemCardHandle::UnMount()
{
	CARD_Unmount(CardSlot);
	Mounted = false;
	SectorSize = 0;
	
	if(SysArea)
	{
		MEM1_free(SysArea);
		SysArea = NULL;
	}
	
	gprintf("card%s unmounted\n", (CardSlot == CARD_SLOTA) ? "A" : "B");
}

void MemCardHandle::ReadSectorSize()
{
	CARD_GetSectorSize(CardSlot, &SectorSize);
}

void MemCardHandle::ResetList()
{
	for(u32 i = 0; i < EntriesList.size(); i++)
		delete EntriesList.at(i);
		
	EntriesList.clear();
}

void MemCardHandle::AddEntry()
{
	GCStruct * newSave = new GCStruct();
	//newSave->filenum = 0;
	memset(newSave->filename, 0x00, CARD_FILENAMELEN);
	memset(newSave->gamecode_str, 0x00, 4);
	memset(newSave->comments, 0x00, CARD_FILENAMELEN*2);
	newSave->permissions = 0;
	newSave->blocks = 0;
	
	EntriesList.push_back(newSave);
}

int MemCardHandle::Open(u8* filename, card_file * CardFile)
{
	int CardError = -1;
	int tries = 0;
	
	while((tries < 50) && (CardError < CARD_ERROR_READY))
	{
		CardError = CARD_Open(CardSlot, (char*)filename, CardFile);
		tries++;
	}
	
	return CardError;
}

GuiGCIcon * MemCardHandle::GetIcon(u8* filename, int width, int height)
{
	card_file CardFile;
	memset(&CardFile, 0, sizeof(card_file));
	
	int CardError = Open(filename, &CardFile);
	if(CardError < CARD_ERROR_READY)
		return NULL;
	
	memset(&gci, 0, sizeof(GCI));
	__card_getstatusex(CardSlot, CardFile.filenum, (struct card_direntry *) &gci);
	
	u8 IconFormat = (gci.icon_fmt & CARD_ICON_MASK);
	if(IconFormat == CARD_ICON_NONE)
	{
		CARD_Close(&CardFile);
		return NULL;
	}
	
	u8 IconAnim = (gci.banner_fmt & CARD_ANIM_MASK);
	u8 IconSpeed = (gci.icon_speed & CARD_SPEED_MASK);
	
	u32 offset = gci.icon_addr;
	std::vector<u32 > IconOffset;
	std::vector<u32 > IconTlutOffset;
	if ((gci.banner_fmt & CARD_BANNER_MASK) == CARD_BANNER_RGB)
		offset += 0x1800;
	else if ((gci.banner_fmt & CARD_BANNER_MASK) == CARD_BANNER_CI)
		offset += (0xc00 + 0x200);
	
	bool sharedPal = false;
	for(int i = 0; i < CARD_MAXICONS; i++)
	{
		if (!GCI_GetIconSpeed(gci.icon_speed, i))
			break;
		
		u8 fmt = GCI_GetIconFmt(gci.icon_fmt,i);
		if(fmt != CARD_ICON_NONE)
		{
			if(fmt == CARD_ICON_CI)
			{
				//! CARD_ICON_CI with shared palette
				IconOffset.push_back(offset);
				offset += 0x400;
				sharedPal = true;
			}
			else if(fmt == CARD_ICON_RGB)
			{
				//! CARD_ICON_RGB
				IconOffset.push_back(offset);
				IconTlutOffset.push_back(0);
				offset += 0x800;
			}
			else
			{
				//! CARD_ICON_CI with own palette
				IconOffset.push_back(offset);
				offset += 0x400;
				IconTlutOffset.push_back(offset);
				offset += 0x200;
			}
		}
	}
	if(sharedPal)
	{
		for(int i = 0; i < (int)IconOffset.size(); i++)
			IconTlutOffset.push_back(offset);
	}
	
	u8* FileBuffer = (u8*)MEM1_memalign(32, MAXFILEBUFFER);
	if(!FileBuffer)
	{
		CARD_Close(&CardFile);
		return NULL;
	}
	memset(FileBuffer, 0, MAXFILEBUFFER);
	
	u32 fileSize = (CardFile.len);
	int bytesdone = 0;
	int blocksize = SectorSize;
	while(bytesdone < (int)fileSize) 
	{
		if((bytesdone + blocksize) > fileSize)
			blocksize = fileSize - bytesdone;
		
		CARD_Read(&CardFile, FileBuffer + bytesdone, blocksize, bytesdone);
		bytesdone += blocksize;
	}
	CARD_Close(&CardFile);
	
	GuiGCIcon * icon = new GuiGCIcon(FileBuffer, IconOffset, IconTlutOffset, IconSpeed, IconFormat, IconAnim, width, height);
	
	MEM1_free(FileBuffer);
	FileBuffer = NULL;
	
	return icon;
}

bool MemCardHandle::SetEntry(int cnt, card_dir * CardDir)
{
	//! open file
	card_file CardFile;
	memset(&CardFile, 0, sizeof(card_file));
	int CardError = Open(CardDir->filename, &CardFile);
	if (CardError < CARD_ERROR_READY)
		return false;
	
	//! add entry
	if(cnt >= (int)EntriesList.size())
		AddEntry();
	
	//! read gci header
	memset(&gci, 0, sizeof(GCI));
	__card_getstatusex(CardSlot, CardFile.filenum, (struct card_direntry *) &gci);
	
	u8* FileBuffer = (u8*)MEM1_memalign(32, MAXFILEBUFFER);
	if(!FileBuffer)
	{
		CARD_Close(&CardFile);
		return NULL;
	}
	memset(FileBuffer, 0, MAXFILEBUFFER);
	memcpy(FileBuffer, &gci, sizeof(GCI));
	
	//! read file
	int FileSize = CardFile.len;
	int bytesdone = 0;
	while(bytesdone < FileSize) 
	{
		CARD_Read (&CardFile, FileBuffer + MCDATAOFFSET + bytesdone, SectorSize, bytesdone);
		bytesdone += SectorSize;
	}
	
	//! set entry
	//EntriesList[cnt]->filenum = CardFile.filenum;
	memcpy(&EntriesList[cnt]->filename, &gci.filename, CARD_FILENAMELEN);
	memcpy(&EntriesList[cnt]->gamecode_str, &gci.gamecode, 4);
	memcpy(&EntriesList[cnt]->comments, FileBuffer + MCDATAOFFSET + gci.comment_addr, 64);
	EntriesList[cnt]->permissions = gci.unknown1;
	EntriesList[cnt]->blocks = gci.filesize8;
	
	//! close file
	CARD_Close(&CardFile);
	MEM1_free(FileBuffer);
	FileBuffer = NULL;
	
	return true;
}

void MemCardHandle::GetList()
{
	ResetList();
	
	int cnt = 0;
	card_dir CardDir;
	memset(&CardDir,0,sizeof(card_dir));
	
	int CardError = CARD_FindFirst(CardSlot, &CardDir, true);
	
	while(CardError != CARD_ERROR_NOFILE)
	{
		if(SetEntry(cnt, &CardDir))
			cnt++;
		
		CardError = CARD_FindNext(&CardDir);
	}
}

GCStruct * MemCardHandle::GetEntry(int index)
{
	if(index < 0 || index >= (int)EntriesList.size())
		return NULL;
	
	return EntriesList[index];
}
